#include "com_log.h"
#include "dev8030.h"
#include "list.h"
#include "rpc_node.h"
#include "timspec_helper.h"
#include "usb_event_list.h"
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "hotplug_rpc.h"

#define DRV_DEBUG           0

#define DRV_DISCONNECT_WAIT_MS 100LLU

typedef struct {
    int     working_id;
    int     drv_fd;
    int     error_no;
    void*   plist;
    uint8_t rcv_buff[20 * 1024];
} drv8030_run_info;

typedef struct {
    char             drv_file_name[128];
    int              dev_id;
    int              started_flg;
    struct list_head node;

    drv8030_run_info dev;

    uint8_t serial_num[4];
    uint8_t ser_len;

    struct timespec disconnect_spec;
    int             disconnect_flg;
    int             disconnect_wait_ms;
} drv8030_info;

typedef struct drvrpc_ctrl {
    dev8030             basedev;
    struct list_head    head; ///< 下挂的drv设备
    pthread_mutex_t     mtx;
    pthread_mutexattr_t mtx_attr;
    int                 working_cnt;
} drvrpc_ctrl;

static int drv_write(void* handle, unsigned char* buff, int len, int timeout)
{
    drv8030_run_info* pdrv = (drv8030_run_info*)handle;

    if (pdrv->error_no) {
        usleep(1000);
        return -1;
    }

#if DRV_DEBUG
    printf("drv : try write len = %d\n", len);
#endif

    while (1) {
        int realwr = len;
        int ret = write(pdrv->drv_fd, buff, realwr);

        if (ret == realwr) {
#if DRV_DEBUG
            com_log(COM_DRV_COM, "drv : write len = %d\n", ret);

            // usbpack pack;
            // ret = unpack_usb_pack(buff, len, 0, &pack);
            // if (ret) {
            //     com_log(COM_DRV_COM, "write pack error!!!\n");
            // }
            // disp_hex(buff, len);
#endif
            return ret;
        } else {
            com_log(COM_DRV_COM, "drv : write err = %d\n", ret);
            pdrv->error_no = -1;
            return -1;
        }
    }

    return 0;
}

static int drv_ev_loop(void* handle)
{
    drv8030_run_info* p8030 = (drv8030_run_info*)handle;

    if (p8030->error_no) {
        usleep(1000);
        return -1;
    }

    struct pollfd pfd = {
        .fd      = p8030->drv_fd,
        .events  = POLLIN | POLLERR,
        .revents = 0,
    };

    int ret = poll(&pfd, 1, 100);
    if (ret < 0) {
        return 0;
    }

    if (pfd.revents & POLLERR) {
        p8030->error_no = -1;
        com_log(COM_DRV_COM, "drv rd: pollerr!!\n");
        return -1;
    }

    if (pfd.revents & POLLIN) {
        int rdlen = read(p8030->drv_fd, p8030->rcv_buff, sizeof(p8030->rcv_buff));

        if (rdlen <= 0) {
            com_log(COM_DRV_COM, "drv : read error = %d\n", rdlen);
        } else {
            // read callback
#if DRV_DEBUG
            com_log(COM_DRV_COM, "drv : read len = %d\n", rdlen);
#endif
            dev_node_list* plist = (dev_node_list*)p8030->plist;
            if (plist->dev_rd_cb) {
                plist->dev_rd_cb(plist->recv_priv, p8030->rcv_buff, rdlen);
            }
        }
    }
    return 0;
}

static int drv_dev_name(void* devhandle, char* buff, int len)
{
    if (!devhandle || !buff || len <= 0) {
        return 0;
    }

    if (len >= 5) {
        strcpy(buff, "drv");
        return 5;
    }
    return 0;
}

static int set_drv_info(drv8030_run_info* pdrv, int fd)
{
    // set drv
    pdrv->error_no = 0;
    pdrv->drv_fd  = fd;

    // set dev_node_list
    dev_node_list* plist = (dev_node_list*)malloc(sizeof(dev_node_list));
    memset(plist, 0, sizeof(dev_node_list));
    plist->devhandle = pdrv;
    plist->dev_send  = drv_write;
    plist->dev_rd_cb = NULL;
    plist->recv_priv = NULL;
    plist->dev_loop  = drv_ev_loop;
    plist->devname   = drv_dev_name;
    plist->running   = run_all;
    pdrv->plist     = plist;

    plist->remote_buff_len         = 0;
    plist->remote_buff_max_payload = 0;

    dev_node_list_init(plist);
    return 0;
}

static drv8030_info* drvrpc_get_info(dev8030* pdev8030, uint32_t workid)
{
    drvrpc_ctrl* pctrl = (drvrpc_ctrl*)pdev8030;

    drv8030_info* ret = NULL;

    drv8030_info* pinfo = NULL;
    list_for_each_entry (pinfo, &pctrl->head, node, drv8030_info) {
        if (pinfo->started_flg == 0) {
            continue;
        }

        if (pinfo->dev.working_id == workid) {
            ret = pinfo;
            break;
        }
    }
    return ret;
}

static void* drvrpc_get_plist(dev8030* pdev8030, uint32_t workid)
{
    drvrpc_ctrl* pctrl = (drvrpc_ctrl*)pdev8030;

    pthread_mutex_lock(&pctrl->mtx);

    drv8030_info* p8030 = drvrpc_get_info(pdev8030, workid);

    if (p8030) {
        pthread_mutex_unlock(&pctrl->mtx);
        return p8030->dev.plist;
    }

    pthread_mutex_unlock(&pctrl->mtx);

    return NULL;
}

static const char* testdrvmac = "testdrvmac";

static int _drvrpc_fill_serial(dev8030* pdev8030, uint32_t workid, uint8_t* buff, size_t len)
{
    drvrpc_ctrl* pctrl = (drvrpc_ctrl*)pdev8030;

    if (!pctrl || !buff || len <= 0) {
        return -1;
    }

    drv8030_info* p8030 = drvrpc_get_info(&pctrl->basedev, workid);

    if (!p8030) {
        return -1;
    }

    if (!p8030->started_flg) {
        return -1;
    }

    int sl = strlen(testdrvmac);
    if (sl <= len) {
        memcpy(buff, testdrvmac, sl);
        return sl;
    }

    return 0;
}

static int drvrpc_fill_serial(dev8030* pdev8030, uint32_t workid, uint8_t* buff, size_t len)
{
    int ret = 0;
    drvrpc_ctrl* pctrl = (drvrpc_ctrl*)pdev8030;

    pthread_mutex_lock(&pctrl->mtx);
    ret = _drvrpc_fill_serial(pdev8030, workid, buff, len);
    pthread_mutex_unlock(&pctrl->mtx);

    return ret;
}

static int drvrpc_getid_all(dev8030* pdev8030, uint32_t* ids, size_t sz)
{
    drvrpc_ctrl* pctrl = (drvrpc_ctrl*)pdev8030;

    if (!pctrl || !ids || sz <= 0) {
        return 0;
    }

    pthread_mutex_lock(&pctrl->mtx);
    sz = sz > pctrl->working_cnt ? pctrl->working_cnt : sz;

    drv8030_info* pdrv = NULL;
    int            cnt   = 0;
    list_for_each_entry (pdrv, &pctrl->head, node, drv8030_info) {
        if (!pdrv->started_flg) {
            continue;
        }

        ids[cnt] = pdrv->dev.working_id;
        if (++cnt >= sz) {
            break;
        }
    }
    pthread_mutex_unlock(&pctrl->mtx);
    return cnt;
}

static int drvrpc_getsz(dev8030* pdev8030)
{
    drvrpc_ctrl* pctrl = (drvrpc_ctrl*)pdev8030;
    return pctrl->working_cnt;
}

static int start_drv_proc(drv8030_info* pdrv)
{
    int fd = open(pdrv->drv_file_name, O_RDWR);
    if (fd < 0) {
        return -1;
    }

    set_drv_info(&pdrv->dev, fd);

    return 0;
}

static int stop_drv(drv8030_info* pdrv)
{
    dev_node_force_exit(pdrv->dev.plist);

    if (pdrv->dev.drv_fd > 0) {
        close(pdrv->dev.drv_fd);
        pdrv->dev.drv_fd = -1;
    }

    return 0;
}

static int drvrpc_8030_poll(dev8030* pdev8030)
{
    int evt = 0;

    drvrpc_ctrl* pctrl = (drvrpc_ctrl*)pdev8030;

    pthread_mutex_lock(&pctrl->mtx);

    drv8030_info* pdrv;
    list_for_each_entry (pdrv, &pctrl->head, node, drv8030_info) {
        if (!pdrv->started_flg) {
            // wait drv stable
            if (pdrv->disconnect_flg) {
                struct timespec now;
#ifdef WIN32
                timespec_get(&now, TIME_UTC);
#else
                clock_gettime(CLOCK_REALTIME, &now);
#endif
                int64_t tickms = get_timespec_diff_in_ms(&pdrv->disconnect_spec, &now);

                if (tickms < pdrv->disconnect_wait_ms) {
                    continue;
                }
                pdrv->disconnect_flg = 0;
            }

            int ret = start_drv_proc(pdrv);
            if (!ret) {
                pctrl->working_cnt++;
                pdrv->started_flg    = 1;
                pdrv->dev.working_id =  pdrv->dev_id;//rpc_dev_get_nxt_working_id(pctrl->basedev.pinfo);
                com_log(COM_DRV_COM, "drv probe ,path = %s , work id = %d\n", pdrv->drv_file_name, pdrv->dev.working_id);

                bb_event_hotplug_t plug_evt = {
                    .id            = pdrv->dev.working_id,
                    .status        = 1,
                };
                memcpy(&plug_evt.bb_mac.mac, pdrv->serial_num, pdrv->ser_len);

                dev8030_hotplug_event(pdev8030->pinfo, &plug_evt);
            }
        } else if (pdrv->dev.error_no) {
            com_log(COM_DRV_COM, "drv detach ,path = %s,work id = %d\n", pdrv->drv_file_name, pdrv->dev.working_id);
            int ret             = stop_drv(pdrv);
            pdrv->dev.error_no = 0;
            pdrv->started_flg  = 0;
            if (!ret) {
                pctrl->working_cnt--;
            }
            pdrv->disconnect_flg = 1;
#ifdef WIN32
            timespec_get(&pdrv->disconnect_spec, TIME_UTC);
#else
            clock_gettime(CLOCK_REALTIME, &pdrv->disconnect_spec);
#endif
            bb_event_hotplug_t plug_evt = {
                .id            = pdrv->dev.working_id,
                .status        = 0,
            };

            dev8030_hotplug_event(pdev8030->pinfo, &plug_evt);
        }
    }

    pthread_mutex_unlock(&pctrl->mtx);
    return 0;
}

extern dev8030_act drv8030_dev_info;

int reg_drvrpc_platform(rpc_info* prpc, const char** devs, int dev_cnt)
{
    com_log(COM_DRV_COM, "reg drv 8030 platform\n");

    drvrpc_ctrl* ctrl = malloc(sizeof(drvrpc_ctrl));

    ctrl->basedev.pact  = &drv8030_dev_info;
    ctrl->basedev.pinfo = prpc;
    pthread_mutex_init(&ctrl->mtx, NULL);

    INIT_LIST_HEAD(&ctrl->head);

    for (int i = 0; i < dev_cnt; i++) {
        drv8030_info* pdrv      = malloc(sizeof(drv8030_info));
        pdrv->started_flg        = 0;
        pdrv->disconnect_flg     = 0;
        pdrv->disconnect_wait_ms = DRV_DISCONNECT_WAIT_MS;
        pdrv->dev_id             = i;
        pdrv->dev.drv_fd         = -1;
        strcpy(pdrv->drv_file_name, devs[i]);
        list_add_tail(&pdrv->node, &ctrl->head);
    }

    rpc_dev_add(prpc, &ctrl->basedev);

    return 0;
}

dev8030_act drv8030_dev_info = {
    .name        = "drv",
    .polldev     = drvrpc_8030_poll,
    .getplist    = drvrpc_get_plist,
    .getsz       = drvrpc_getsz,
    .getid_all   = drvrpc_getid_all,
    .fill_serial = drvrpc_fill_serial,
};
